查看原文
其他

详解主流Java应用服务器的工作原理及组件设计(有彩蛋)

2018-03-06 刘光瑞 DBAplus社群


本文根据DBAplus社群第138期线上分享整理而成,文末还有好书送哦~


讲师介绍

刘光瑞

《Tomcat架构解析》作者


  • 现任窝客研发总监,负责窝客产品研发管理及总体架构设计。

  • 拥有十几年企业级大型业务系统研发架构经验,成功带领团队设计并研发基于Tomcat的组件化微服务架构。

  • 热衷于系统架构、Java技术栈及应用服务器中间件的学习及研究。


今天所讲内容的大纲如下:

  1. Java应用服务器分类

  2. Servlet容器工作原理

  3. Tomcat组件及请求处理



一、Java应用服务器分类


大概从2000年以后,Java应用服务器的使用主要经历了3个阶段:J2EE应用服务器、Servlet容器及Web服务器、响应式微服务。


1、J2EE应用服务器


首先,是J2EE应用服务器。这部分在以Spring为主的without EJB的应用框架出现之前,基本上是企业应用开发的标配。这类应用服务器严格实现了J2EE规范,通过了SUN的相关认证,还支持标准的基于J2EE的应用开发,包括EJB、JMS、JTA、Servlet、JSF等等。



这类应用服务器有很强的配置和优化能力,非常适合集中式的大型企业级应用开发。这一类的产品情况如下:



像WebSphere这一类商用产品,是大型的银行、电信应用的主流服务器,甚至配合IBM的JVM实现,有着非常好的性能优势。


免费的诸如JBOSS,使用也非常广泛,作为商用企业级应用服务器的减配版。


企业级应用服务器的特点就是过重。大概2010年之后(具体时间不确定),有几个转向了OSGi这种模块化架构,期望提供一种更加灵活的架构。像JBoss这一类产品在互联网规模化、轻量化的大背景下,似乎使用的相对越来越少。但是在集中式的企业级应用场景,它们应该还是主流,轻量级服务器是很难取代的。


2、Servlet容器及Web服务器


这一类在后面才提到,并不是说它们的历史较短,像Tomcat的历史可以说是最长的几款服务器实现。而是说它们被大规模用于生产环境的尝试,相对较晚。


这是与互联网应用大规模分布式架构分不开的。在轻量级的架构中,绝大多数应用实际上主要使用了服务器的Servlet容器部分,其它部分很少使用,而是使用更完善的第三方框架去替代。这时候,J2EE服务器显得过重,所以开始更倾向于只使用Servlet容器提供HTTP服务。



这一类服务器见图片,主要是Tomcat、Resin、Jetty、Undertow。



Tomcat大家想必都很熟悉了,基本Java Web应用开发入门就使用。它主要的使用场景还是独立启动部署Web应用,嵌入式的场景与Jetty相比,要相对差一些;Undertow出现相对晚一些,它是JBoss实现的新的Servlet容器,以前JBoss直接使用Tomcat作为Servlet容器。


如果大家基于Spring Boot开发应用,想必都知道,现在Spring Boot对这三款容器都支持,现在用的比较多的是Tomcat和Jetty,Undertow使用的相对较少。


后两者是很少作为独立启动的服务器来使用的,尤其是Undertow,应该不支持这种场景。


3、响应式微服务


这一类严格说并不是Web服务器。有时候(如微服务架构),我们更倾向于直接提供HTTP服务,这个时候并不需要支持Web,不需要Servlet规范。



这一类就是为这种场景设计的,目前有两类方案可以来满足这种场景,一种是Vert.x,这个国内目前使用应该不是很多。主要是它不支持Servlet规范,这估计是一方面的原因。


还有一种是Play Framework内置的基于事件的HTTP服务,和Vert.x类似,它们都是为高并发的互联网服务开发实现的。今天对于这种方案我们不做展开,还是以主流的Servlet方案为主。



二、Servlet容器工作原理


应用服务器基于Servlet提供基础的HTTP服务,包括Web服务、SOA,即便是JSP页面,最终也是通过Servlet实现的。因此Servlet容器可以说是Web服务的核心。



1、容器的核心部分


对于一款Servlet容器,主要分为两部分:链接器和容器。前者是服务器对I/O的封装,后者是对Servlet规范的实现。



大家可以简要看一下这一篇的内容:一款服务器通过链接器读取网络请求,将其转换为符合Servlet规范的Request对象,同时负责将Servlet容器的响应输出到客户端。


它对Servlet容器屏蔽了协议及I/O方式等的区别。无论是HTTP还是AJP、还是HTTPS,在容器中获取到的都是一个标准的Request对象。



而容器呢,主要负责三个方面的工作:

  • 按照Servlet规范(Web.xml/注解等等),识别部署的Web应用,将其解析为内部的请求处理组件;

  • 接收链接器的请求调用(一般不需要容器负责请求映射,映射部分是链接器完成的);

  • 按照Servlet规范进行请求处理,这部分主要是Filter等的处理。



2、工作原理


大家可以看一下这个示意图:



对于链接器,无论采用哪一种I/O,都是采用一种线程池的方式来处理的。


主要有两个线程池,一个用于接收请求,一个用于处理请求。接收请求的线程负责读取请求头,完成请求映射,并将请求提交到映射到的请求处理组件(一个指定的Servlet)。


这儿大家注意一点,容器映射完成的结果是一个Servlet,而不是一个Web应用,至少Tomcat是这样处理的,Jetty更轻量。


虽然Servlet的具体实现不同,但是完成的工作基本类似,而且模式也类似(除去我们说的第三类)。


接下来我们具体看一下Tomcat是如何实现Servlet容器的:相对于Jetty轻量的处理架构,Tomcat更像“服务器”一些,它考虑了一些服务器的特性,如虚拟主机。



三、Tomcat组件及请求过程


1、容器主要组件


Tomcat的主要组件可以看一下下面这张图:它底层通过Java命名服务统一对各种组件进行管理,体现了服务器软件的“管理”特性;然后是链接器实现Coyote和Jasper JSP引擎。



Tomcat与Jetty相比,它更是一款完整的Servlet容器方案。而Jetty是以模块化的形式提供了Servlet容器以及各种HTTP工具集。像JSP引擎,Jetty直接使用的Tomcat中的Jasper(还有另一种可选方案)。


最上面的Catalina是Tomcat容器的实现。大家如果看Tomcat的异常堆栈,就会发现包名基本都是以这个作为开头。


Tomcat链接器,我们可以简单的按两层进行划分:应用层和传输层。



应用层里,最新版本的Tomcat支持HTTP、AJP以及HTTP2,传输层支持NIO、NIO2以及APR,最新版本已经不支持BIO。



链接器还有一个很重要的组件就是线程池,想必做过基本的Tomcat优化的人都调整过Tomcat并发链接数。线程池的分类我们刚才也说过了,分为接收线程池和请求处理线程池。


2、Tomcat Servlet容器组件


Servlet容器的组件见图:简单来说,Tomcat通过一种分层的架构使得Servlet容器有很好的灵活性,而且通过生命周期管理接口,为这些各层的对象提供统一的生命周期管理。



分层如下:Engine-Host-Context-Wrapper。引擎是顶级,我们可以认为就是容器本身。Host是虚拟主机的概念,这样可以在Tomcat配置多个虚拟主机。Context就是我们的Web应用,Wrapper是Web应用中的Servlet,所以还是刚才说的,Tomcat中的请求映射是体现到Servlet上。


基于分层的组件,Tomcat还提供了完善的生命周期事件处理机制,Web应用的自动扫描启动、热部署、卸载等都是通过事件实现的。如果阅读Tomcat代码,需要重点关注下面的生命周期监听。



HostConfig主要用于扫描Web应用,自动根据Web应用目录创建Context。ContextConfig重点处理Servlet规范,生成Servlet、Filter等各种规范组件。最后,Tomcat自身也实现了一种链式的请求处理机制,这一点和规范中的Filter类似,这就是Valve。


在Tomcat中,Filter中可以做的事情,Valve可以做;Valve可以做的事情,Filter却不一定可以做,需要深入和服务器交互的工作,一般都是用Valve实现。


3、请求处理过程


这是一张Tomcat请求处理过程的时序图,大家看一下就会发现。Tomcat在这种分层的容器设计方案下,请求处理是很简单的,重点在前期的请求接收及I/O处理(Processor的各种实现)、Mapper的映射以及Wrapper中的规范处理。



4、Tomcat的优化


最后一点内容,我们再概要介绍一下Tomcat的优化。今天介绍的相对比较基础,大家感兴趣的还是需要去看相关的专门的书籍。


Tomcat优化主要但不限于以下四个方面:线程池、协议、JVM以及I/O,之所以说不限于,是因为应用服务器的优化,不仅要去优化其自身,诸如操作系统等也是要统一考虑的。



线程池就是我们刚才说的两类。接收线程池一般都是CPU内核数即可。当然如果服务器核数非常多,可以适当调小。这个线程池处理速度比请求处理线程池要快的多,因此要考虑这两者的匹配,原因PPT中都介绍了。



做请求处理线程池这个设置时,最好做一些基准测试,并没有什么精确的经验值。


协议层面。这个最常见的就是开启HTTP GZip压缩。如果配置了前置的Web服务器,那么静态文件可以考虑直接放到Web服务器上,然后可以修改HTTP链接器的I/O方式。新版本的Tomcat默认是NIO,可以改为APR,后者可以充分发挥本地调用的优势,与直接使用Apache基本是类似的。



当然对于前置了Web服务器的,可以采用AJP协议与Web服务器链接。Apache应该没问题,Nginx好像没有官方的AJP实现。第三方的就需要考虑健壮性和支持程度了。



然后是JVM,这部分估计最常见的就是调整JVM的堆内存。对于垃圾回收算法,也需要根据具体的应用场景进行调整。是吞吐量优先还是响应时间优先,需要使用多少线程去处理,这些都需要在具体的硬件配置下进行具体的测试才行。



今天的内容到此就结束了,主要内容还是非常概要,每一部分大家如果想深入了解,都需要去阅读相关书籍。协议、规范、JVM,这些都是必不可少的。

彩蛋来了

在本文微信订阅号(dbaplus)评论区留下足以引起共鸣的真知灼见,小编将在本文发布后的隔天中午12点选出留言内容最精彩的3位读者,送出以下好书一本~

新规说明:同一个月份里,已获赠者将不可重复拿书。

特别鸣谢图灵社区为活动供图书赞助。


近期热文

提前排雷!分布式缓存的25个优秀实践与线上案例

借鉴Codis实现的两种不停机分库分表迁移方案

从Elasticsearch集群及数据层架构,看分布式系统设计

DBA+工具:SQL自审自上线,摆脱人肉审核就在当下 

关联与下钻:快速定位MySQL性能瓶颈的制胜手段


最新活动

2018 Gdevops全球敏捷运维峰会(成都站)

↓↓↓点这里了解更多报名详情

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存